[AWS CDK] API Gateway(REST API)のCORSの動作を確認してみた
こんにちは、CX事業本部 IoT事業部の若槻です。
今回は、AWS CDKで実装したAPI Gateway(REST API)のCORS(Cross-origin resource sharing)の動作を確認してみました。
REST APIにおけるCORS
基本的に下記ドキュメントに記載の内容です。
Cross-origin resource sharing(CORS)とは、ブラウザで実行されているスクリプトから開始されるクロスオリジンHTTPリクエストを制限するブラウザのセキュリティ機能です。
クロスオリジンリクエストは、シンプルなリクエストとシンプルでないリクエストの2種類に分けられます。
以下の条件がすべて該当する場合はシンプルなリクエストとなります。
- GET、HEAD、およびPOSTのいずれかのメソッドのリクエストである
- POSTメソッドリクエストの場合、Originヘッダーを含んでいる
- リクエストのペイロードコンテンツタイプが text/plain、multipart/form-data、または application/x-www-form-urlencoded のいずれかである
- リクエストにカスタムヘッダーが含まれていない
- シンプルなリクエストに関する Mozilla CORS のドキュメントに一覧表示されている追加要件。
REST APIのリソースがクロスオリジンのシンプルでないリクエストを受け取る場合はCORSを有効にする必要があります。
確認してみた
シンプルなリクエストの場合(GET)
シンプルなリクエストのCORSの動作確認はGETリクエストで行ってみます。
レスポンスを返すLambdaのコードです。
exports.handler = async (event) => { const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; return response; };
LambdaとREST APIをLambdaプロキシ統合で実装するCDKスタックのコードです。
import * as cdk from "@aws-cdk/core"; import * as lambda from "@aws-cdk/aws-lambda"; import * as apigateway from "@aws-cdk/aws-apigateway"; export class AwsCdkAppStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const testFunction = new lambda.Function(this, "TestFunction", { code: lambda.Code.fromAsset("src/lambda/handlers"), functionName: "testFunction", handler: "index.handler", runtime: lambda.Runtime.NODEJS_14_X, memorySize: 128, }); const restApi = new apigateway.RestApi(this, "RestApi", { restApiName: "test", deployOptions: { stageName: "v1", }, }); const dataResource = restApi.root.addResource("data"); dataResource.addMethod( "GET", new apigateway.LambdaIntegration(testFunction) ); } }
cdk deploy
してリソースをデプロイします。
まず、ブラウザからREST APIのエンドポイントにアクセスすると、Lambdaからのレスポンスを表示させられました。
次に、下記のWebアプリ(React)からAxiosでGETリクエストをして確認してみます。
import React, { useState, useEffect } from "react"; import "./App.css"; import axios from "axios"; const endpointUri = process.env.REACT_APP_REST_API_ENDPOINT_URI!; const App: React.FC = () => { const [body, setBody] = useState<any>(); useEffect(() => { axios.get(endpointUri).then((res) => { setBody(res.data); }); }, []); return <div className="App">{body}</div>; }; export default App;
Webアプリへ接続すると、CORSエラーとなりました。
ドキュメントによるとシンプルなクロスオリジンリクエストの場合はAccess-Control-Allow-Origin
ヘッダーが必要とのことです。(記載にはPOST メソッドリクエストの場合とありますが、ここはGETなどそれ以外のリクエストにも該当するようです)
シンプルなクロスオリジン POST メソッドリクエストの場合、リソースからのレスポンスには Access-Control-Allow-Origin ヘッダーを含める必要があります。ここで、ヘッダーキーの値は '*' (任意のオリジン) に設定されるか、そのリソースへのアクセスが許可されているオリジンに設定されます。
そこでLambdaのコードでレスポンスヘッダーを下記のように追加します。
exports.handler = async (event) => { const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), headers: { "Access-Control-Allow-Origin": "*" }, }; return response; };
cdk deploy
をしてLambdaを修正し、Webアプリに再度接続すると、正常にLambdaからのレスポンスを取得できました。
シンプルでないリクエストの場合(POST)
シンプルでないリクエストのCORSの動作確認はPOSTリクエストで行ってみます。
下記のWebアプリ(React)からAxiosでPOSTリクエストをして確認してみます。
import React, { useState, useEffect } from "react"; import "./App.css"; import axios from "axios"; const endpointUri = process.env.REACT_APP_REST_API_ENDPOINT_URI!; const App: React.FC = () => { const [body, setBody] = useState<any>(); useEffect(() => { axios.post(endpointUri).then((res) => { setBody(res.data); }); }, []); return <div className="App">{body}</div>; }; export default App;
まずバックエンドの修正を行わずPOSTリクエストをすると、CORSエラーとなりました。これは想定通りです。
REST APIのルートリソースにdefaultCorsPreflightOptions
を設定して、CORSを有効にします。
import * as cdk from "@aws-cdk/core"; import * as lambda from "@aws-cdk/aws-lambda"; import * as apigateway from "@aws-cdk/aws-apigateway"; export class AwsCdkAppStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const testFunction = new lambda.Function(this, "TestFunction", { code: lambda.Code.fromAsset("src/lambda/handlers"), functionName: "testFunction", handler: "index.handler", runtime: lambda.Runtime.NODEJS_14_X, memorySize: 128, }); const restApi = new apigateway.RestApi(this, "RestApi", { restApiName: "test", deployOptions: { stageName: "v1", }, defaultCorsPreflightOptions: { allowOrigins: apigateway.Cors.ALL_ORIGINS, allowMethods: apigateway.Cors.ALL_METHODS, allowHeaders: apigateway.Cors.DEFAULT_HEADERS, statusCode: 200, }, }); restApi.root.addMethod( "GET", new apigateway.LambdaIntegration(testFunction) ); restApi.root.addMethod( "POST", new apigateway.LambdaIntegration(testFunction) ); } }
cdk deploy
でスタックの変更をデプロイします。
ここでマネジメントコンソールからREST APIのリソースを見てみると、ルートにOPTIONS
メソッドがきちんと追加されていることが確認できます。
WebアプリでPOSTリクエストを行うと、正常にLambdaからのレスポンスを取得できました。
参考
- AWS CDKでAPI Gatewayのカスタムドメイン環境を構築してみた | DevelopersIO
- API Gateway の Lambda プロキシ統合のCORS対応をまとめてみる | DevelopersIO
- amazon cloudformation - enabling CORS for AWS API gateway with the AWS CDK - Stack Overflow
- CDKのNodejsFunctionを使ってREST API作ってみた - Qiita
- aws-cdk apigateway lambda cors対応
- apigateway: add explicit support for CORS · Issue #906 · aws/aws-cdk
- Lambda + API GatewayでCORSを有効にしているのにCORSでエラーになる - Qiita
以上